package com.itcast.peft.service;

import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

import org.apache.commons.lang3.StringUtils;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import com.itcast.peft.config.MultipleModelConfig.ChatModelHolder;
import com.itcast.peft.entity.Answer;
import com.itcast.peft.entity.LineMessage;
import com.itcast.peft.entity.Question;
import com.itcast.peft.entity.RoleMessage;
import com.itcast.peft.mapper.AnswerMapper;
import com.itcast.peft.mapper.QuestionMapper;
import com.itcast.peft.service.HealthReportAnalyzer.HealthData;
import com.itcast.peft.service.InteractionDataWasher.WashedData;
import com.itcast.peft.util.AsyncWrapper;
import com.itcast.peft.util.ExceptionWrapper;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class AnswerService {
    
    @Autowired
    private QuestionMapper questionMapper;
    
    @Autowired
    private AnswerMapper answerMapper;
    
    @Autowired
    private AsyncWrapper asyncWrapper;
    
    @Resource
    private ChatModelHolder distillTeacherModelHolder;
    
    @Resource
    private ChatModelHolder distillStudentModelHolder;
    
    @Resource
    private ChatModelHolder userprefOptModelHolder;
    
    @Resource
    private ChatModelHolder healthOptModelHolder;
    
    private ChatClient teacherClient;
    
    private ChatClient studentClient;
    
    private ChatClient userOptClient;
    
    private ChatClient healthOptClient;
    
    @PostConstruct
    void init() {
        var assistant = """
                你是一个计算机领域的教育专家，你须要对学生提问的题目输出一份标准答案，它由几个固定章节组成：
                1.考察点解析：析分学生提问的问题所属的知识体系
                2.思路解析：即要如何回答问题，回答应该包括哪些要点
                3.标准答案内容：输出标准答案
                4.代码举例：结合问题内容，适当给出代码样例
                5.总结与附带说明：总结核心要点，附带说明
                你的回答必须按顺序包含以上五块内容，返回内容中不用对你自己的身份进行说明，专心输出内容即可。
                """;
        this.teacherClient = ChatClient.builder(distillTeacherModelHolder.getModel()).defaultSystem(assistant).build();
        this.studentClient = ChatClient.builder(distillStudentModelHolder.getModel()).build();
        this.userOptClient = ChatClient.builder(userprefOptModelHolder.getModel()).build();
        this.healthOptClient = ChatClient.builder(healthOptModelHolder.getModel())
                .defaultSystem("你是一个专业的医生").build();
    }
    
    @Async
    public void answerByTeacher(String batchId) {
        var uuid = UUID.randomUUID().toString();
        var group = String.format("teacher-%s-%s", distillTeacherModelHolder.getConfig().getModelName(), uuid);
        var qlist = questionMapper.selectListByBatchId(batchId);
        var futureList = qlist.stream().map(q -> CompletableFuture.supplyAsync(() -> answer(teacherClient ,q, group), this.asyncWrapper.getExecutor())).toList();
        var answerList = futureList.stream()
            .map(ExceptionWrapper.<CompletableFuture<Answer>, Answer>wrapToRTE(CompletableFuture::get))
            .toList();
        answerMapper.insert(answerList, 100);
        log.info("batch answer compeleted:{}", group);
    }
    
    @Async
    public void answerByStudent(String batchId) {
        var uuid = UUID.randomUUID().toString();
        var group = String.format("student-%s-%s", distillStudentModelHolder.getConfig().getModelName(), uuid);
        var qlist = questionMapper.selectListByBatchId(batchId);
        var futureList = qlist.stream().map(q -> CompletableFuture.supplyAsync(() -> answer(studentClient ,q, group), this.asyncWrapper.getExecutor())).toList();
        var answerList = futureList.stream()
            .map(ExceptionWrapper.<CompletableFuture<Answer>, Answer>wrapToRTE(CompletableFuture::get))
            .toList();
        answerMapper.insert(answerList, 100);
        log.info("batch answer compeleted:{}", group);
    }
    
    @Async
    public void answerByUserOpt(String batchId) {
        var uuid = UUID.randomUUID().toString();
        var group = String.format("useropt-%s-%s", userprefOptModelHolder.getConfig().getModelName(), uuid);
        var qlist = questionMapper.selectListByBatchId(batchId);
        var futureList = qlist.stream().map(q -> CompletableFuture.supplyAsync(() -> answer(userOptClient ,q, group), this.asyncWrapper.getExecutor())).toList();
        var answerList = futureList.stream()
            .map(ExceptionWrapper.<CompletableFuture<Answer>, Answer>wrapToRTE(CompletableFuture::get))
            .toList();
        answerMapper.insert(answerList, 100);
        log.info("batch answer compeleted:{}", group);
    }
    
    @Async
    public void answerByHealthOpt(String batchId) {
        var uuid = UUID.randomUUID().toString();
        var group = String.format("healthopt-%s-%s", healthOptModelHolder.getConfig().getModelName(), uuid);
        var qlist = questionMapper.selectListByBatchId(batchId);
        
        var futureList = qlist.stream().map(q -> CompletableFuture.supplyAsync(() -> answer(healthOptClient ,q, group), this.asyncWrapper.getExecutor())).toList();
        var answerList = futureList.stream()
            .map(ExceptionWrapper.<CompletableFuture<Answer>, Answer>wrapToRTE(CompletableFuture::get))
            .toList();
        answerMapper.insert(answerList, 100);
        log.info("batch answer compeleted:{}", group);
    }
    
    public void addWashedData(List<WashedData> data, ChatModelHolder orgModelHolder) {
        var uuid = UUID.randomUUID().toString();
        var group = String.format("washed-%s-%s", orgModelHolder.getConfig().getModelName(), uuid);
        var ansList = data.stream().map(w -> {
            var result = new Answer();
            result.setAnswer(w.getPreferanceReply().getContent());
            result.setQuestionId(w.getTargetQuestion().getId());
            result.setModelGroup(group);
            return result;
        }).toList();
        answerMapper.insert(ansList, 100);
        log.info("batch answer compeleted:{}", group);
    }
    
    public void addHealthData(List<HealthData> data, ChatModelHolder orgModelHolder) {
        var uuid = UUID.randomUUID().toString();
        var group = String.format("health-%s-%s", orgModelHolder.getConfig().getModelName(), uuid);
        var ansList = data.stream().map(h -> {
            var result = new Answer();
            result.setAnswer(h.getReportJson());
            result.setQuestionId(h.getQuestionId());
            result.setModelGroup(group);
            return result;
        }).toList();
        answerMapper.insert(ansList, 100);
        log.info("batch answer compeleted:{}", group);
    }
    
    private Answer answer(ChatClient cli,Question q, String group) {
        var result = new Answer();
        var opt = OpenAiChatOptions.builder()
                .temperature(0d)
                .build();
        var prompt = new Prompt(q.getQuestion(), opt);
        var ans = cli.prompt(prompt).call().content();
        result.setAnswer(ans);
        result.setQuestionId(q.getId());
        result.setModelGroup(group);
        log.info("{} answered.", StringUtils.abbreviate(q.getQuestion(), 64));
        return result;
    }
    
    public List<LineMessage> convertAnswerGroupToLineMessage(String ansGroup,String systemMessage) {
        var qMap = answerMapper.questionId2dataByGroup(ansGroup);
        return questionMapper.selectByIds(qMap.keySet()).stream().map(q -> {
            List<RoleMessage> rm = new LinkedList<>();
            Optional.ofNullable(systemMessage).ifPresent(s -> rm.add(new RoleMessage("system", s)));
            rm.add(new RoleMessage("user", q.getQuestion()));
            rm.add(new RoleMessage("assistant", qMap.get(q.getId()).getAnswer()));
            return new LineMessage(rm);
        }).toList();
    }
}
